
/*-------------------------------------------------------------------------
 *
 * basebackup_target.c
 *	  基本备份可以是“目标化”的，这意味着它们可以发送到
 *	  除请求备份的客户端以外的其他地方。
 *	  此外，可以通过扩展定义新目标。此文件
 *	  包含支持该功能的代码。
 *
 * Portions Copyright (c) 2010-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/backup/basebackup_target.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "backup/basebackup_target.h"
#include "utils/memutils.h"

typedef struct BaseBackupTargetType
{
	char	   *name;
	void	   *(*check_detail) (char *, char *);
	bbsink	   *(*get_sink) (bbsink *, void *);
} BaseBackupTargetType;

struct BaseBackupTargetHandle
{
	BaseBackupTargetType *type;
	void	   *detail_arg;
};

static void fc_initialize_target_list(void);
static bbsink *fc_blackhole_get_sink(bbsink *fc_next_sink, void *fc_detail_arg);
static bbsink *fc_server_get_sink(bbsink *fc_next_sink, void *fc_detail_arg);
static void *fc_reject_target_detail(char *fc_target, char *fc_target_detail);
static void *fc_server_check_detail(char *fc_target, char *fc_target_detail);

static BaseBackupTargetType builtin_backup_targets[] =
{
	{
		"blackhole", fc_reject_target_detail, fc_blackhole_get_sink
	},
	{
		"server", fc_server_check_detail, fc_server_get_sink
	},
	{
		NULL
	}
};

static List *BaseBackupTargetTypeList = NIL;

/*
 * 添加一个新的基础备份目标类型。
 *
 * 这旨在供服务器扩展使用。
 */
void BaseBackupAddTarget(char *fc_name,
					void *(*fc_check_detail) (char *, char *),
					bbsink *(*fc_get_sink) (bbsink *, void *))
{
	BaseBackupTargetType *fc_newtype;
	MemoryContext fc_oldcontext;
	ListCell   *fc_lc;

	/* 如果目标列表尚未初始化，请先初始化。 */
	if (BaseBackupTargetTypeList == NIL)
		fc_initialize_target_list();

	/* 在目标类型列表中搜索具有此名称的现有条目。 */
	foreach(fc_lc, BaseBackupTargetTypeList)
	{
		BaseBackupTargetType *fc_ttype = lfirst(fc_lc);

		if (strcmp(fc_ttype->name, fc_name) == 0)
		{
			/*
			 * 我们找到了一个，所以更新它。
			 *
			 * 对于同一个名称多次调用 BaseBackupAddTarget 可能不是一个好主意，
			 * 但是如果发生这种情况，这似乎是最理智的行为。
			 */
			fc_ttype->check_detail = fc_check_detail;
			fc_ttype->get_sink = fc_get_sink;
			return;
		}
	}

	/*
	 * 我们在这里使用 TopMemoryContext 进行分配，以确保我们需要的数据
	 * 不会在我们面前消失；这也是我们将目标名称复制到新分配的内存块中的原因。
	 */
	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);
	fc_newtype = palloc(sizeof(BaseBackupTargetType));
	fc_newtype->name = pstrdup(fc_name);
	fc_newtype->check_detail = fc_check_detail;
	fc_newtype->get_sink = fc_get_sink;
	BaseBackupTargetTypeList = lappend(BaseBackupTargetTypeList, fc_newtype);
	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * 查找基础备份目标并验证 target_detail。
 *
 * 定义新备份目标的扩展程序可能会定义一个新的 bbsink 类型以匹配。
 * target_detail 的验证可以在此处调用的 check_detail 例程中完成，也可以在
 * 从 BaseBackupGetSink 调用的 bbsink 构造函数中完成。这主要是品味问题，
 * 但 check_detail 函数运行得稍早一些。
 */
BaseBackupTargetHandle *
BaseBackupGetTargetHandle(char *fc_target, char *fc_target_detail)
{
	ListCell   *fc_lc;

	/* 如果目标列表尚未初始化，请先初始化。 */
	if (BaseBackupTargetTypeList == NIL)
		fc_initialize_target_list();

	/* 在目标类型列表中搜索匹配项。 */
	foreach(fc_lc, BaseBackupTargetTypeList)
	{
		BaseBackupTargetType *fc_ttype = lfirst(fc_lc);

		if (strcmp(fc_ttype->name, fc_target) == 0)
		{
			BaseBackupTargetHandle *fc_handle;

			/* 找到了目标。 */
			fc_handle = palloc(sizeof(BaseBackupTargetHandle));
			fc_handle->type = fc_ttype;
			fc_handle->detail_arg = fc_ttype->check_detail(fc_target, fc_target_detail);

			return fc_handle;
		}
	}

	/* 未找到目标。 */
	ereport(ERROR,
			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
			 errmsg("unrecognized target: \"%s\"", fc_target)));

	/* 保持编译器安静 */
	return NULL;
}

/*
 * 构造一个将实现备份目标的 bbsink。
 *
 * get_sink 函数完成所有实际工作，因此我们在这里要做的只是
 * 用正确的参数调用它。check_detail 函数返回的内容
 * 在此处传递给 get_sink 函数。这让这两个函数如果愿意可以相互通信。
 * 如果不想，check_detail 函数可以简单地返回 target_detail，并让
 * get_sink 函数从那里继续处理。
 */
bbsink * BaseBackupGetSink(BaseBackupTargetHandle *fc_handle, bbsink *fc_next_sink)
{
	return fc_handle->type->get_sink(fc_next_sink, fc_handle->detail_arg);
}

/*
 * 将预定义的目标类型加载到 BaseBackupTargetTypeList 中。
 */
static void fc_initialize_target_list(void)
{
	BaseBackupTargetType *fc_ttype = builtin_backup_targets;
	MemoryContext fc_oldcontext;

	fc_oldcontext = MemoryContextSwitchTo(TopMemoryContext);
	while (fc_ttype->name != NULL)
	{
		BaseBackupTargetTypeList = lappend(BaseBackupTargetTypeList, fc_ttype);
		++fc_ttype;
	}
	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * 通常，get_sink 函数应该构造并返回实现备份目标的新 bbsink，
 * 但 'blackhole' 目标只是抛弃数据。我们可以通过添加一个仅转发的 bbsink
 * 来实现，但不添加 bbsink 实现这一点会更便宜。
 */
static bbsink * fc_blackhole_get_sink(bbsink *fc_next_sink, void *fc_detail_arg)
{
	return fc_next_sink;
}

/*
 * 创建一个实现服务器端备份的 bbsink。
 */
static bbsink * fc_server_get_sink(bbsink *fc_next_sink, void *fc_detail_arg)
{
	return bbsink_server_new(fc_next_sink, fc_detail_arg);
}

/*
 * 实现一个不接受 detail 的目标的 target-detail 检查。
 */
static void * fc_reject_target_detail(char *fc_target, char *fc_target_detail)
{
	if (fc_target_detail != NULL)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("target \"%s\" does not accept a target detail",
						fc_target)));

	return NULL;
}

/*
 * 实现服务器端备份的 target-detail 检查。
 *
 * target_detail 应该是备份应该写入的目录的名称，但我们在这里不检查。
 * 而是，这个检查，以及必要的权限检查，发生在 bbsink_server_new 中。
 */
static void * fc_server_check_detail(char *fc_target, char *fc_target_detail)
{
	if (fc_target_detail == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("target \"%s\" requires a target detail",
						fc_target)));

	return fc_target_detail;
}
